package de.servicezombie.lang;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import org.springframework.util.ResourceUtils;

public class ConfigurationSystemPropertyAwareImpl implements Configuration, ToProperties {

	private static final Logger log = LoggerFactory.getLogger(ConfigurationSystemPropertyAwareImpl.class);

	private Properties properties;
	private File file;

	public ConfigurationSystemPropertyAwareImpl(String systemPropertyName) throws FileNotFoundException, IOException,
			IllegalArgumentException {
		if (log.isDebugEnabled()) {
			log.debug("SystemPropertyAwareConfiguration: >> " + systemPropertyName);
		}

		String fileLocation = System.getProperty(systemPropertyName);
		log.info("Load file " + fileLocation);

		Assert.notNull(fileLocation, "Please set property " + systemPropertyName + " to a valid file location");

		file = ResourceUtils.getFile(fileLocation);
		FileReader reader = new FileReader(file);
		properties = new Properties();
		properties.load(reader);

		if (log.isDebugEnabled()) {
			log.debug("SystemPropertyAwareConfiguration: << " + this);
		}
	}

	@Override
	public String toString() {
		return getClass().getSimpleName() + "[" + file.getAbsolutePath() + "]";
	}

	private String[] asArrayOrNull(String key) {
		String result = properties.getProperty(key);
		if (result == null)
			return null;
		String[] asArray = StringUtils.split(result, ", ;\t");
		return asArray;
	}
	
	public String[] getStringArray(String key) {		
		String[] array = asArrayOrNull(key);
		assertMandatoryValue(key, array);
		return array;
	}

	@SuppressWarnings("unchecked")
	private <T> T marshallFromString(Class<T> requiredType, String elem) {
		T value;
		try {
			Method method = requiredType.getMethod("valueOf", String.class);
			value = (T) method.invoke(null, elem);
		}
		catch (InvocationTargetException e) {
			throw new BadConfigurationException(e.getCause().getMessage(), e.getCause());
		}
		catch (Exception e) {
			throw new BadConfigurationException(e.getMessage(), e);
		}

		return value;
	}

	private void assertMandatoryValue(String key, String[] propertyValue) {
		if(propertyValue == null) {
			throw new BadConfigurationException("Mandatory value is null: " + key);
		}
	}

	private void assertMandatoryValue(String key, String propertyValue) {
		if(propertyValue == null) {
			throw new BadConfigurationException("Mandatory value is null: " + key);
		}
	}

	/* (non-Javadoc)
	 * @see de.servicezombie.lang.Configuration#getList(java.lang.String, java.lang.Class)
	 */
	@Override
	public <T> List<T> getList(String key, Class<T> requiredType) {

		String[] asArray = asArrayOrNull(key);
		assertMandatoryValue(key, asArray);

		List<T> resultAsList = new ArrayList<T>(asArray.length);

		for (String elem : asArray) {
			T value = marshallFromString(requiredType, elem);
			resultAsList.add(value);
		}

		return resultAsList;
	}

	/* (non-Javadoc)
	 * @see de.servicezombie.lang.Configuration#getOptionalList(java.lang.String, java.util.List, java.lang.Class)
	 */
	@Override
	public <T> List<T> getOptionalList(String key, List<T> defaultValue, Class<T> requiredType) {

		String[] asArray = asArrayOrNull(key);
		if (asArray == null) {
			return defaultValue;
		}

		List<T> resultAsList = new ArrayList<T>(asArray.length);

		for (String elem : asArray) {
			T value = marshallFromString(requiredType, elem);
			resultAsList.add(value);
		}

		return resultAsList;
	}

	/* (non-Javadoc)
	 * @see de.servicezombie.lang.Configuration#getString(java.lang.String)
	 */
	@Override
	public String getString(String key) {
		String result = properties.getProperty(key);
		assertMandatoryValue(key, result);
		return result;
	}

	/* (non-Javadoc)
	 * @see de.servicezombie.lang.Configuration#getOptionalString(java.lang.String, java.lang.String)
	 */
	@Override
	public String getOptionalString(String key, String defaultValue) {
		String result = properties.getProperty(key);
		return result;
	}

	/* (non-Javadoc)
	 * @see de.servicezombie.lang.Configuration#getInt(java.lang.String)
	 */
	@Override
	public int getInt(String key) {
		String result = getOptionalString(key, null);
		assertMandatoryValue(key, result);
		return Integer.valueOf(result).intValue();
	}

	/* (non-Javadoc)
	 * @see de.servicezombie.lang.Configuration#getOptionalInt(java.lang.String, int)
	 */
	@Override
	public int getOptionalInt(String key, int defaultValue) {
		String result = getOptionalString(key, null);
		return result != null ? Integer.valueOf(result).intValue() : defaultValue;
	}

	/* (non-Javadoc)
	 * @see de.servicezombie.lang.Configuration#getBool(java.lang.String)
	 */
	@Override
	public boolean getBool(String key) {
		String result = getOptionalString(key, null);
		assertMandatoryValue(key, result);
		return Boolean.valueOf(result);
	}

	/* (non-Javadoc)
	 * @see de.servicezombie.lang.Configuration#getOptionalBool(java.lang.String, boolean)
	 */
	@Override
	public boolean getOptionalBool(String key, boolean defaultValue) {
		String result = getOptionalString(key, null);
		return result != null ? Boolean.valueOf(result) : defaultValue;
	}

	@Override
	public Properties toProperties() {
		return properties;
	}

}
